package net.erl.objects.pattern.impl;

import net.erl.objects.pattern.ErlPattern;
import net.erl.objects.pattern.EP;
import net.erl.objects.pattern.EPMatchable;
import net.erl.objects.ET;
import net.erl.objects.ErlTerm;
import net.erl.objects.ErlList;

import java.util.Map;

/**
 * A list pattern.
 */
public class ErlListPattern extends ErlPatternBase {

    private ErlPattern[] heads;
    private ErlPattern tail;

    /**
     * Build a pattern matching an empty list.
     */
    public ErlListPattern() {
        heads = new ErlPattern[0];
        tail = EP.pterm(ET.list());
    }

    /**
     * Build a pattern of the kind: [X1|...|Xn|Rest].
     * @param items [X1,...,Xn,Rest].
     */
    public ErlListPattern(EPMatchable... items) {
        if (items.length == 0) {
            heads = new ErlPattern[0];
            tail = EP.pterm(ET.list());
        } else {
            heads = new ErlPattern[items.length-1];
            for (int i = 0 ; i < items.length-1 ; i++) {
                heads[i] = items[i] instanceof ErlTerm
                        ? EP.pterm((ErlTerm)items[i])
                        : (ErlPattern)items[i];
            }
            EPMatchable last = items[items.length-1];
            if (last instanceof ErlList) {
                tail = EP.pterm((ErlList)last);
            } else if (last instanceof ErlPattern) {
                tail = (ErlPattern)last;
            } else {
                throw new IllegalArgumentException("pattern tail must be a list or pattern");
            }
        }
    }

    public Map<String, ErlTerm> matchAndSet(ErlTerm term, Map<String, ErlTerm> initialVars) {
        if (! (term instanceof ErlList)) {
            return null;
        }

        ErlList list = (ErlList)term;
        Map<String,ErlTerm> vars = ErlPatternBase.ensureVars(initialVars);
        for (ErlPattern head: heads) {
            if (list.isEmptyList()) {
                return null;
            }
            vars = head.matchAndSet(list.hd(), vars);
            if (vars == null) {
                return null;
            }
            list = list.tl();
        }
        return tail.matchAndSet(list, vars);
    }

}